use chrono::NaiveDate;
use scraper::{ Html, Selector };
use serde::{ Deserialize, Serialize };
use serde_json::Value;
use std::error::Error;
use std::str::FromStr;

/// mapping to [stock_all_page_1.html](file:///Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/sample/stock_all_page_1.html)
#[derive(Debug)]
pub struct StockA {
    pub code: String, //1
    pub name: String,
    pub current_price: f64,
    pub change_percentage: f64,
    pub change_amount: f64,
    pub speed_percentage: f64,
    pub turnover_rate: f64,
    pub volume_ratio: f64,
    pub amplitude_percentage: f64,
    pub trading_volume: f64,
    pub floating_share: f64,
    pub floating_market_value: f64,
    pub price_earnings_ratio: f64,
    // 添加其他字段如果需要
}

impl StockA {
    pub fn extract_stock_data(html: &str) -> Result<Vec<StockA>, Box<dyn Error>> {
        let document = Html::parse_document(html);
        let table_selector = Selector::parse("table.m-table.m-pager-table tbody tr")?;

        let mut stocks = Vec::new();

        for row in document.select(&table_selector) {
            let code = row
                .select(&Selector::parse("td a")?)
                .nth(0)
                .map(|a| a.text().collect::<String>())
                .unwrap_or_default();

            let name = row
                .select(&Selector::parse("td a")?)
                .nth(1)
                .map(|a| a.text().collect::<String>())
                .unwrap_or_default();

            let current_price: f64 = row
                .select(&Selector::parse("td")?)
                .nth(3)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let change_percentage: f64 = row
                .select(&Selector::parse("td")?)
                .nth(4)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let change_amount: f64 = row
                .select(&Selector::parse("td")?)
                .nth(5)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let speed_percentage: f64 = row
                .select(&Selector::parse("td")?)
                .nth(6)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let turnover_rate: f64 = row
                .select(&Selector::parse("td")?)
                .nth(7)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let volume_ratio: f64 = row
                .select(&Selector::parse("td")?)
                .nth(8)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let amplitude_percentage: f64 = row
                .select(&Selector::parse("td")?)
                .nth(9)
                .map(|td| f64::from_str(td.text().collect::<String>().trim()).unwrap_or(0.0))
                .unwrap_or(0.0);

            let trading_volume: f64 = row
                .select(&Selector::parse("td")?)
                .nth(10)
                .map(|td| Self::convert_cn_number(td.text().collect::<String>().trim()))
                .unwrap_or(0.0);

            let floating_share: f64 = row
                .select(&Selector::parse("td")?)
                .nth(11)
                .map(|td| Self::convert_cn_number(td.text().collect::<String>().trim()))
                .unwrap_or(0.0);

            let floating_market_value: f64 = row
                .select(&Selector::parse("td")?)
                .nth(12)
                .map(|td| Self::convert_cn_number(td.text().collect::<String>().trim()))
                .unwrap_or(0.0);

            let price_earnings_ratio: f64 = row
                .select(&Selector::parse("td")?)
                .nth(13)
                .map(|td| Self::convert_cn_number(td.text().collect::<String>().trim()))
                .unwrap_or(0.0);

            let stock = StockA {
                code,
                name,
                current_price,
                change_percentage,
                change_amount,
                speed_percentage,
                turnover_rate,
                volume_ratio,
                amplitude_percentage,
                trading_volume,
                floating_share,
                floating_market_value,
                price_earnings_ratio,
                // Add other fields as needed
            };

            stocks.push(stock);
        }

        Ok(stocks)
    }

    pub fn convert_cn_number(cnm: &str) -> f64 {
        let multiplier = match cnm.chars().last() {
            Some('万') => 10_000.0,
            Some('亿') => 100_000_000.0,
            _ => 1.0, // No suffix, so multiplier is 1
        };

        // Remove the suffix if it exists
        let number_str = cnm.replace("万", "").replace("亿", "").replace("亏损", "-100");

        // Parse the number string
        let number: f64 = match number_str.parse::<f64>() {
            Ok(n) => n,
            Err(_) => {
                eprintln!("Failed to parse number from string: {}", number_str);
                return 0.0; // Return 0.0 or an error value on failure
            }
        };

        // Apply the multiplier
        number * multiplier
    }
}

/// mapping to [stock_hs_688347_day_k_after/before/none.json](file:///Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/sample/stock_hs_688347_day_k_none.json)
#[derive(Debug, Serialize, Deserialize)]
pub struct StockDayK {
    total: Value,
    start: String,
    name: String,
    #[serde(rename = "marketType")]
    market_type: String,
    #[serde(rename = "sortYear")]
    sort_year: Vec<Vec<i32>>,
    #[serde(rename = "priceFactor")]
    price_factor: u32,
    price: String,
    volumn: String,
    #[serde(rename = "afterVolumn")]
    after_volumn: String,
    dates: String,
    #[serde(rename = "issuePrice")]
    issue_price: String,
}

impl StockDayK {
    pub fn prices(&self) -> Vec<DayKPrice> {
        let mut dayk_prices: Vec<DayKPrice> = DayKPrice::parse_dayk_price_string(&self.price);
        let dates: Vec<&str> = self.dates.split(',').collect();

        let total_loops: usize = dates.len();

        let mut current_loop: usize = 0;
        let mut current_total_loops: usize = 0;
        let mut current_index: usize = 0;
        while current_total_loops < total_loops {
            if current_loop < (self.sort_year[current_index][1] as usize) {
            } else {
                // Move to the next year if the current year's loop count is reached
                current_index += 1;
                // Reset the loop count for the new year
                current_loop = 0;
                println!("");
            }
            // Print the year if we're within the current year's loop count
            // println!(
            //     "{current_total_loops}-{current_index}:( {}-{} )-{} ",
            //     self.sort_year[current_index][0], dates[current_total_loops], current_loop
            // );
            if let Some(price) = dayk_prices.get_mut(current_total_loops) {
                let date_str = format!(
                    "{}{}",
                    self.sort_year[current_index][0],
                    dates[current_total_loops]
                );
                if let Ok(parsed_date) = NaiveDate::parse_from_str(&date_str, "%Y%m%d") {
                    price.date = parsed_date;
                    // println!("Parsed Date: {}", parsed_date);
                } else {
                    println!("Error parsing the date.");
                }
            }

            // Increment the loop count
            current_loop += 1;
            current_total_loops += 1;
        }

        dayk_prices
    }
}

#[derive(Debug)]
pub struct DayKPrice {
    pub date: NaiveDate,
    pub low: i32,
    pub open: i32,
    pub high: i32,
    pub close: i32,
}

impl DayKPrice {
    pub fn parse_dayk_price_string(s: &str) -> Vec<DayKPrice> {
        let numbers: Vec<&str> = s.split(',').collect();
        let mut dayk_vec = Vec::new();

        for i in (0..numbers.len()).step_by(4) {
            if i + 3 < numbers.len() {
                let low = numbers[i].parse::<i32>().unwrap_or(0);
                let open = numbers[i + 1].parse::<i32>().unwrap_or(0);
                let high = numbers[i + 2].parse::<i32>().unwrap_or(0);
                let close = numbers[i + 3].parse::<i32>().unwrap_or(0);
                dayk_vec.push(DayKPrice {
                    date: NaiveDate::from_str("20220101").unwrap_or_default(),
                    low,
                    open,
                    high,
                    close,
                });
            } else {
                // Handle error: not enough numbers for a complete DayK
                break;
            }
        }

        dayk_vec
    }
}

pub struct Stock;

impl Stock {
    /// Referer: https://q.10jqka.com.cn/
    /// https://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/1/ajax/1/
    pub async fn stock_page() {
        // GET /index/index/board/all/field/zdf/order/desc/page/1/ajax/1/ HTTP/1.1
        // Accept: text/html, */*; q=0.01
        // Accept-Encoding: gzip, deflate, br
        // Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
        // Cache-Control: no-cache
        // Connection: keep-alive
        // Cookie: _ga=GA1.1.1109415927.1705805520; _ga_KQBDS1VPQF=GS1.1.1705805519.1.1.1705805558.0.0.0; Hm_lvt_929f8b362150b1f77b477230541dbbc2=1706782074; Hm_lvt_722143063e4892925903024537075d0d=1706782090; Hm_lvt_78c58f01938e4d85eaf619eae71b4ed1=1706782074; spversion=20130314; Hm_lpvt_722143063e4892925903024537075d0d=1708336190; Hm_lpvt_929f8b362150b1f77b477230541dbbc2=1708336191; historystock=688347%7C*%7C300578%7C*%7C300068%7C*%7C300270; Hm_lpvt_78c58f01938e4d85eaf619eae71b4ed1=1708649592; v=A2K4Ge3pc2AUiG88vNsStRDstePBs2a5GLda8az7jlWAfwxdlEO23ehHqgl_
        // Host: q.10jqka.com.cn
        // Pragma: no-cache
        // Referer: https://q.10jqka.com.cn/
        // Sec-Fetch-Dest: empty
        // Sec-Fetch-Mode: cors
        // Sec-Fetch-Site: same-origin
        // User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36
        // X-Requested-With: XMLHttpRequest
        // hexin-v: A2K4Ge3pc2AUiG88vNsStRDstePBs2a5GLda8az7jlWAfwxdlEO23ehHqgl_
        // sec-ch-ua: "Not A(Brand";v="99", "Google Chrome";v="121", "Chromium";v="121"
        // sec-ch-ua-mobile: ?0
        // sec-ch-ua-platform: "macOS"
    }
}

pub fn add_two_numbers(a: i32, b: i32) -> i32 {
    a + b
}

#[cfg(test)]
mod test {
    use crate::REQWEST_CLIENT;

    use super::*;
    use std::{ fs::{ self, File }, io::BufReader };

    /// Can not get Cookie the cookies might not be directly set in the response headers ,
    /// Refer to [can I use tauri-apps tool to get cookie from a thirdparty url which I can not controll over ?](file:///Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/README.md)
    #[tokio::test]
    async fn test_get_cookie() {
        // Send the initial request to capture cookies
        let response = REQWEST_CLIENT.get("https://q.10jqka.com.cn/")
            .send().await
            .expect("Failed to send request");

        // Extract cookies from the response headers manually
        if let Some(cookie_header) = response.headers().get("Set-Cookie") {
            let cookies = cookie_header.to_str().unwrap();
            println!("Cookies: {}", cookies);
        } else {
            println!("No Set-Cookie header found in the response");
        }

        // Extract the cookies from the response headers
        // The response.cookies() method in Reqwest is used to extract cookies from the response headers.
        // If you are not able to find any cookies using response.cookies(), it could be due to the way the cookies are set in the response headers.
        //
        // In some cases, the cookies might not be directly set in the response headers but may be managed using JavaScript on the client side
        // after the initial response is received. In such cases, you may not see the cookies in the response headers that can be extracted
        // using response.cookies().
        let cookies = response.cookies().collect::<Vec<_>>();

        println!("cookies: {:?}", cookies);

        // // Create a new client with the captured cookies
        // let cookie_jar = CookieJar::from_cookies(cookies);
        let client_with_cookies = reqwest::Client::builder().cookie_store(true).build().unwrap();

        // // Now you can use client_with_cookies to send requests with the captured cookies
        // let mut response = client_with_cookies
        //     .get("https://q.10jqka.com.cn/")
        //     .send()
        //     .unwrap();

        // // Read the response body or do whatever you need with it
        let body = response.text().await.unwrap();
        println!("body: {}", body);
    }

    /// Please copy Cookie from chrome-dev-total_loops [股票清单](https://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/2/ajax/1/)
    #[tokio::test]
    async fn test_chunk_gzip_gbk() {
        let url =
            "https://q.10jqka.com.cn/index/index/board/all/field/zdf/order/desc/page/2/ajax/1/";

        let client = reqwest::Client
            ::builder()
            .redirect(reqwest::redirect::Policy::limited(5)) // Follow up to 5 redirects
            .build()
            .unwrap();

        let cookie =
            "_ga=GA1.1.1109415927.1705805520; _ga_KQBDS1VPQF=GS1.1.1705805519.1.1.1705805558.0.0.0; Hm_lvt_929f8b362150b1f77b477230541dbbc2=1706782074; Hm_lvt_722143063e4892925903024537075d0d=1706782090; Hm_lvt_78c58f01938e4d85eaf619eae71b4ed1=1706782074; spversion=20130314; Hm_lpvt_722143063e4892925903024537075d0d=1708336190; Hm_lpvt_929f8b362150b1f77b477230541dbbc2=1708336191; historystock=688347%7C*%7C300578%7C*%7C300068%7C*%7C300270; Hm_lpvt_78c58f01938e4d85eaf619eae71b4ed1=1708649592; v=A5NJBrSy8meNo74zVMhzknn3JBy4SCfK4dxrPkWw77LpxL3CzRi3WvGs-5pW";
        let mut response = REQWEST_CLIENT.get(url)
            .header("Accept-Encoding", "gzip, deflate, br")
            .header("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8")
            .header("Cache-Control", "no-cache")
            .header("Connection", "keep-alive")
            .header("Cookie", cookie)
            .header("Host", "q.10jqka.com.cn")
            .header("Referer", "https://q.10jqka.com.cn/")
            .send().await
            .expect("Failed to send request");

        println!("response={:?}", response);

        let mut body = Vec::new();

        // handling Transfer-Encoding: chunked
        while let Some(chunk) = response.chunk().await.unwrap() {
            body.extend_from_slice(&chunk);
        }

        // handling Content-Type: text/html; charset=gbk
        let charset = response
            .headers()
            .get("Content-Type")
            .and_then(|content_type| {
                content_type
                    .to_str()
                    .ok()
                    .and_then(|content_type_str| {
                        content_type_str
                            .split(";")
                            .find(|part| part.trim().starts_with("charset="))
                            .map(|charset_part| {
                                let rs = charset_part.trim().trim_start_matches("charset=").trim();
                                println!("rs={} charset_part={}", rs, charset_part);
                                rs
                            })
                    })
            })
            .unwrap_or("utf-8");

        let encoding = encoding_rs::Encoding
            ::for_label(charset.as_bytes())
            .unwrap_or(encoding_rs::UTF_8);
        println!("charset={charset} encoding={:?}", encoding);
        let (decoded, _, _) = encoding.decode(&body);
        println!("handling Content-Type: text/html; charset=gbk Decoded body={}", decoded);
    }

    #[tokio::test]
    async fn test_extract_stock_data_page_1() {
        let html = fs
            ::read_to_string(
                "/Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/sample/stock_all_page_1.html"
            )
            .unwrap(); // Read HTML content from a file
        let stocks = StockA::extract_stock_data(&html).unwrap();
        assert_eq!(stocks.len(), 20);

        if let Some(first) = stocks.first() {
            assert_eq!(first.code, "688683");
            assert_eq!(first.name, "莱尔科技");
            assert_eq!(first.current_price, 16.13);
            assert_eq!(first.change_percentage, 20.02);
            assert_eq!(first.change_amount, 2.69);
            assert_eq!(first.speed_percentage, 0.0);
            assert_eq!(first.turnover_rate, 8.14);
            assert_eq!(first.volume_ratio, 2.05);
            assert_eq!(first.amplitude_percentage, 18.23);
            assert_eq!(first.trading_volume, 81169900.0);
            assert_eq!(first.floating_share, 63177900.0);
            assert_eq!(first.floating_market_value, 1019000000.0);
            assert_eq!(first.price_earnings_ratio, 78.35);
        }
    }

    #[tokio::test]
    async fn test_extract_stock_data_page_2000() {
        let html = fs
            ::read_to_string(
                "/Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/sample/stock_all_page_2000.html"
            )
            .unwrap(); // Read HTML content from a file
        let stocks = StockA::extract_stock_data(&html).unwrap();
        assert_eq!(stocks.len(), 0);
    }

    #[tokio::test]
    async fn test_parse_stock_688347_day_k_none() {
        let path =
            "/Users/CC/gitee/conanchen/fuquants-workspace/crates/fudacli/x10jqka/sample/stock_hs_688347_day_k_none.json";
        // Open the JSON path and read its contents
        let path = File::open(path).expect(&format!("Failed to open path={}", path));
        let reader = BufReader::new(path);

        // Parse the JSON data into the Data struct
        let stock_dayk: StockDayK = serde_json::from_reader(reader).expect("Failed to parse JSON");
        let dayk_prices = stock_dayk.prices();
        let dates: Vec<&str> = stock_dayk.dates.split(',').collect();
        assert_eq!(dates.len(), 99 + 30 - 1);
        assert_eq!(dayk_prices.len(), dates.len());
        if let Some(price) = dayk_prices.get(0) {
            assert_eq!(price.date, NaiveDate::from_ymd_opt(2023, 08, 07).unwrap());
        }
        if let Some(price) = dayk_prices.get(98) {
            assert_eq!(price.date, NaiveDate::from_ymd_opt(2023, 12, 29).unwrap());
        }
        if let Some(price) = dayk_prices.get(99) {
            assert_eq!(price.date, NaiveDate::from_ymd_opt(2024, 01, 02).unwrap());
        }
        if let Some(price) = dayk_prices.get(127) {
            assert_eq!(price.date, NaiveDate::from_ymd_opt(2024, 02, 19).unwrap());
        }

        // println!("dates={:?}", dates);
        println!(
            "dayk_prices.len()= {} \ndayk_prices[0]= {:?}",
            dayk_prices.len(),
            dayk_prices.first()
        );
        if let Some(total_str) = stock_dayk.total.as_str() {
            // 处理字符串类型的total
            assert_eq!(total_str, "129");
        } else if let Some(total_num) = stock_dayk.total.as_i64() {
            // 处理数字类型的total
            assert_eq!(total_num, 129);
        }
    }

    #[tokio::test]
    async fn test_convert_cn_number() {
        let result1 = StockA::convert_cn_number("8116.99万");
        let result2 = StockA::convert_cn_number("10.19亿");
        let result3 = StockA::convert_cn_number("123.45");
        let result4 = StockA::convert_cn_number("亏损");

        assert_eq!(result1, 81169900.0);
        assert_eq!(result2, 1019000000.0);
        assert_eq!(result3, 123.45);
        assert_eq!(result4, -100.0);
    }

    #[tokio::test]
    async fn test_parse_dayk_price_string() {
        let input = "5230,658,758,76,5101,39,315,238";
        let dayk_prices = DayKPrice::parse_dayk_price_string(input);
        assert_eq!(dayk_prices.len(), 2);
        if let Some(first) = dayk_prices.first() {
            assert_eq!(first.low, 5230);
            assert_eq!(first.open, 658);
            assert_eq!(first.high, 758);
            assert_eq!(first.close, 76);
        }
    }

    #[tokio::test]
    async fn adds_two_positive_numbers() {
        let result = add_two_numbers(2, 3);
        assert_eq!(result, 5);
    }

    #[tokio::test]
    async fn adds_two_negative_numbers() {
        let result = add_two_numbers(-2, -3);
        assert_eq!(result, -5);
    }

    #[tokio::test]
    async fn adds_positive_and_negative_number() {
        let result = add_two_numbers(2, -3);
        assert_eq!(result, -1);
    }
}
